/*
 * OREKIT-X
 * Copyright 2002-2008 CS Communication & Systemes
 * 
 * Parts of this software package have been licensed to CS
 * Communication & Systemes (CS) under one or more contributor license
 * agreements.  See the NOTICE file distributed with this work for
 * additional information.
 *  
 * This is an experimental copy of OREKIT from www.orekit.org.
 * Please use the original OREKIT from orekit.org for normal work
 * unrelated to this research project.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License.  You
 * may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.  See the License for the specific language governing
 * permissions and limitations under the License.
 */
package ore.propagation.events;

import ore.SpacecraftState;
import ore.errors.OrekitException;
import ore.propagation.sampling.OrekitStepInterpolator;
import ore.time.AbsoluteDate;

import org.apache.commons.math.ConvergenceException;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

/**
 * This class manages several {@link EventDetector event detectors}
 * during propagation.
 *
 * This class is heavily based on the {@link
 * org.apache.commons.math.ode.events.CombinedEventsManager} class
 * from the Apache commons-math library. The changes performed consist
 * in replacing raw types (double and double arrays) with space
 * dynamics types ({@link AbsoluteDate}, {@link SpacecraftState}).
 * 
 * @see EventDetector
 */
public class CombinedEventsDetectorsManager 
    extends Object
    implements java.io.Serializable
{
    /** 
     * Detectors states.
     */
    private List<EventState> states;
    /** 
     * First active event.
     */
    private EventState first;
    /** 
     * Initialization indicator.
     */
    private boolean initialized;


    /** 
     * Create empty 
     */
    public CombinedEventsDetectorsManager() {
        super();
        this.states      = new ArrayList<EventState>();
    }
    /**
     * Copy
     */
    public CombinedEventsDetectorsManager(CombinedEventsDetectorsManager copy) {
        super();
        this.states = new ArrayList<EventState>(copy.states);
    }


    /** 
     * @param detector Event detector to add
     */
    public void addEventDetector(final EventDetector detector) {
        this.states.add(new EventState(detector));
    }
    /** 
     * @return Copy of the collection of the event detectors
     */
    public Collection<EventDetector> getEventsDetectors() {
        List<EventDetector> list = new ArrayList<EventDetector>(this.states.size());
        for (EventState state : this.states) {
            list.add(state.getEventDetector());
        }
        return list;
    }
    /** 
     * Remove all the events detectors that have been added to the
     * handler.
     */
    public void clearEventsDetectors() {
        this.states.clear();
    }
    /** 
     * Evaluate the impact of the proposed step on all handled events
     * detectors.
     * 
     * @param interpolator step interpolator for the proposed step
     * 
     * @return true if at least one event detector triggers an event
     * before the end of the proposed step (this implies the step should
     * be rejected)
     * 
     * @exception OrekitException The interpolator fails to compute
     * the function somewhere within the step
     * @exception ConvergenceException An event cannot be located
     */
    public boolean evaluateStep(OrekitStepInterpolator interpolator)
        throws OrekitException, ConvergenceException
    {
        this.first = null;
        if (this.states.isEmpty()) {
            /*
             * There is nothing to do, return now to avoid setting the
             * interpolator time (and hence avoid unneeded calls to
             * the user function due to interpolator finalization)
             */
            return false;
        }
        else if (!this.initialized) {
            /*
             * Initialize the events states
             */
            AbsoluteDate t0 = interpolator.getPreviousDate();
            interpolator.setInterpolatedDate(t0);
            SpacecraftState y = interpolator.getInterpolatedState();
            for (EventState state : this.states) {
                state.reinitializeBegin(y);
            }
            this.initialized = true;
        }
        /*
         * Check events occurrence
         */
        for (EventState state : states) {

            if (state.evaluateStep(interpolator)) {
                if (this.first == null)
                    this.first = state;
                else {
                    if (interpolator.isForward()) {
                        if (state.getEventTime().compareTo(this.first.getEventTime()) < 0) {
                            this.first = state;
                        }
                    }
                    else {
                        if (state.getEventTime().compareTo(this.first.getEventTime()) > 0) {
                            this.first = state;
                        }
                    }
                }
            }

        }
        return (this.first != null);
    }
    /** 
     * Get the occurrence time of the first event triggered in the
     * last evaluated step.
     * 
     * @return Occurrence time of the first event triggered in the last
     * evaluated step, or null if no event is triggered.
     */
    public AbsoluteDate getEventTime() {
        return (this.first == null) ? null : this.first.getEventTime();
    }
    /** 
     * Inform the events detectors that the step has been accepted by
     * the propagator.
     * 
     * @param spacecraftState State value at the end of the step
     * 
     * @exception OrekitException The value of one of the events
     * detectors cannot be evaluated
     */
    public void stepAccepted(SpacecraftState spacecraftState)
        throws OrekitException
    {
        for (EventState eventState : this.states) {
            eventState.stepAccepted(spacecraftState);
        }
    }
    /** 
     * @return The propagation should be stopped at the end of the
     * current step.
     */
    public boolean stop() {
        for (EventState state : this.states) {
            if (state.stop()) {
                return true;
            }
        }
        return false;
    }
    /**
     * Conditional reset of the event detectors.
     *  
     * If several detectors reset the state at the same time, the
     * former changes will be overriden by later changes, and only the
     * last change will be returned. 
     * 
     * A better way to handle this is to use only one event detector
     * for simultaneous changes.
     * 
     * @param oldSpacecraftState value of the state vector at the
     * beginning of the next step
     * 
     * @return New state (oldState if no reset is needed)
     * 
     * @exception OrekitException One of the event detectors that
     * should reset the state fails to do it
     */
    public SpacecraftState reset(SpacecraftState oldSpacecraftState)
        throws OrekitException
    {
        SpacecraftState newSpacecraftState = oldSpacecraftState;
        for (EventState eventState : this.states) {
            newSpacecraftState = eventState.reset(newSpacecraftState);
        }
        return newSpacecraftState;
    }
}
